﻿Imports System.Text.RegularExpressions
Imports System.Windows.Forms
Imports System.Drawing

Public Class SearchableListBox
    Inherits ListBox

    Private _searchText As String

    Public Sub New()
        MyBase.DrawMode = Windows.Forms.DrawMode.OwnerDrawVariable
    End Sub


    Public Property SearchText As String
        Get
            Return _searchText
        End Get
        Set(ByVal value As String)
            _searchText = value
            Refresh()
        End Set
    End Property
    Protected Overrides Sub OnMeasureItem(e As System.Windows.Forms.MeasureItemEventArgs)
        If Me.Items.Count = 0 Then
            Exit Sub
        End If
        Dim text = Me.Items(e.Index).ToString()
        Dim stringSize As SizeF = e.Graphics.MeasureString(text, Font)
        e.ItemHeight = CInt(stringSize.Height)
        e.ItemWidth = CInt(stringSize.Width)
    End Sub
    Protected Overrides Sub OnDrawItem(e As System.Windows.Forms.DrawItemEventArgs)
        Debug.WriteLine("Draw Item: " & e.Index)
        e.DrawBackground()
        'We don't have to do any hilighting if there are no items
        If Me.Items.Count = 0 Then
            'Show the name of the SearchListBox as the first item in design mode
            If Me.DesignMode Then
                e.Graphics.DrawString(Me.Name, e.Font, Brushes.Black, e.Bounds, StringFormat.GenericDefault)
            End If
            Exit Sub
        End If
        Dim lForeBrush As Brush = New SolidBrush(e.ForeColor)
        Dim lHilightBrush As Brush = New SolidBrush(Color.FromArgb(&H98, &HE9, &HE9, &HE))
        Dim lItemText As String = Me.Items(e.Index).ToString()

        Dim lMatches = RegExHelper.GetRegExMatches(lItemText, Me.SearchText)


        Dim lItemStringFormat As New StringFormat With {.FormatFlags = StringFormatFlags.NoClip}
        If lMatches IsNot Nothing Then
            Dim lCharRanges As New List(Of CharacterRange)
            Dim lSafeSearchText As String = Nothing
            'Convert matchs to List(of CharacterRange)
            For Each match As Match In lMatches
                If match.Success AndAlso match.Length > 0 Then
                    lCharRanges.Add(New CharacterRange(match.Index, match.Length))
                End If
            Next match
            lItemStringFormat.SetMeasurableCharacterRanges(lCharRanges.ToArray)
            Dim matchedregion() As Region = e.Graphics.MeasureCharacterRanges(lItemText, e.Font, e.Bounds, lItemStringFormat)
            For Each rgn As Region In matchedregion
                Dim lMatchedWordRegion = rgn.GetBounds(e.Graphics)
                lMatchedWordRegion.Inflate(0, -1)
                e.Graphics.FillRectangle(lHilightBrush, Rectangle.Round(lMatchedWordRegion))
                rgn.Dispose()
            Next rgn
        End If
        e.Graphics.DrawString(lItemText, e.Font, lForeBrush, e.Bounds, lItemStringFormat)
        e.DrawFocusRectangle()
        lForeBrush.Dispose()
        lHilightBrush.Dispose()
    End Sub

    'Force OwnerDrawVariable
    'Thanks for the suggestion: Acamar http://social.msdn.microsoft.com/profile/acamar/?ws=usercard-mini
    <System.ComponentModel.Browsable(False)> _
    <System.ComponentModel.EditorBrowsable(False)> _
    Public Overrides Property DrawMode As System.Windows.Forms.DrawMode
        Get
            Return Windows.Forms.DrawMode.OwnerDrawVariable
        End Get
        Set(value As System.Windows.Forms.DrawMode)
            'MyBase.DrawMode = Windows.Forms.DrawMode.OwnerDrawVariable
        End Set
    End Property

    Protected Overrides Sub OnSizeChanged(e As System.EventArgs)
        MyBase.OnSizeChanged(e)
        Refresh()
    End Sub

End Class